iT邦幫忙

2025 iThome 鐵人賽

DAY 29
0
自我挑戰組

IT工具與自我學IT的過程分享系列 第 29

系列主題Day 2|你的專屬折扣:用 AI 把「線下猶豫」變「線上轉換」——三天從商機洞察到上線驗證

  • 分享至 

  • xImage
  •  

Day 2|技術拆解:擷取 → 比對 → 推播 → 回寫

目標:把 ESP32-CAM → 雲端比對(向量/人臉)→ 看板/APP 推播 → 數據回寫 的骨架一次搭好,並給你能用的最小程式骨架。


0) TL;DR

  • 前端:ESP32-CAM 取像,HTTP 上傳 JPEG。模組成本數百元、內建 OV2640,最高可 1600×1200。
  • 後端:Flask(GCP VM/Cloud Run)接收影像,走 Amazon Rekognition多模態向量做相似度判定,再回傳素材 URL。
  • 輸出:Chromecast with Google TV(Kiosk 模式)播對應廣告頁;同時把互動/轉換寫入資料庫做 Dashboard。

1) 架構與資料流

[ESP32-CAM] --HTTP/JPEG--> [Flask 後端(GCP)] --API--> [比對引擎]
      |                        |                        ├─ Amazon Rekognition (人臉)
      |                        |                        └─ Multimodal Embedding (向量)
      |                        |                              ↑ 距離門檻: high=0.447 / med=0.632
      |                        |                              (餘弦相似輔助)
      |                        └──────────┬──────────┘
      |                                   │
      └──(時間戳/點位)──→ [SQLite/日誌]    └→ [推播建議/素材URL] → [數位看板/APP]
                                          └→ [Dashboard 指標回寫]

向量門檻(報告摘錄):high=0.447medium=0.632;實測 Pair A1-A2 距離 0.408(高信心),A1-B1 距離 0.634(傾向不同個體)。


2) 前端:ESP32-CAM 上傳影像(最小可用)

Arduino(ESP32-CAM)→ Flask /upload:

#include <WiFi.h>
#include "esp_camera.h"
#include <HTTPClient.h>

const char* ssid="YOUR_WIFI";
const char* pwd ="YOUR_PASS";
String server = "http://<YOUR_SERVER>:8000/upload";

void setup(){
  WiFi.begin(ssid, pwd);
  while(WiFi.status()!=WL_CONNECTED) delay(500);

  // 省略:camera_config_t 初始化(OV2640, JPEG, SVGA/VGA)
  // 建議:事件觸發時再拍照,或每 X 秒拍一張
}

void loop(){
  camera_fb_t* fb = esp_camera_fb_get();
  if(!fb) return;

  HTTPClient http;
  http.begin(server);
  String boundary = "----esp32form";
  http.addHeader("Content-Type","multipart/form-data; boundary="+boundary);

  WiFiClient *s = http.getStreamPtr();
  String head = "--"+boundary+"\r\nContent-Disposition: form-data; name=\"cam_id\"\r\n\r\ncam-001\r\n";
  head += "--"+boundary+"\r\nContent-Disposition: form-data; name=\"image\"; filename=\"snap.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n";
  String tail = "\r\n--"+boundary+"--\r\n";

  int size = head.length() + fb->len + tail.length();
  http.addHeader("Content-Length", String(size));
  http.collectHeaders(nullptr,0);
  s->print(head); s->write(fb->buf, fb->len); s->print(tail);

  int code = http.POST(nullptr,0);
  http.end();
  esp_camera_fb_return(fb);

  delay(3000); // 節流,實務上依場景調整
}

為什麼選 ESP32-CAM? 價格數百元、內建 OV2640、支援 Wi-Fi,HTTP 直傳到伺服器即可。


3) 後端:Flask 收檔 → 串 API → 回傳素材

# server.py
from flask import Flask, request, jsonify
from datetime import datetime
import sqlite3, os

app = Flask(__name__)

def save_event(cam_id, path, decision, score):
  conn = sqlite3.connect("events.db"); cur = conn.cursor()
  cur.execute("""CREATE TABLE IF NOT EXISTS events(
      ts TEXT, cam TEXT, path TEXT, decision TEXT, score REAL)""")
  cur.execute("INSERT INTO events VALUES (?,?,?,?,?)",
              (datetime.utcnow().isoformat(), cam_id, path, decision, score))
  conn.commit(); conn.close()

@app.post("/upload")
def upload():
  img = request.files["image"]
  cam = request.form.get("cam_id","cam-001")
  ts  = datetime.utcnow().strftime("%Y%m%dT%H%M%S")
  os.makedirs("uploads", exist_ok=True)
  path = f"uploads/{cam}_{ts}.jpg"; img.save(path)

  # 1) 呼叫 Rekognition 或 向量 API(此處以偽代碼示意)
  # score, decision = face_or_vector_match(path)  # decision: 'match' / 'unknown'

  # 先用假資料跑通流程:
  score, decision = 0.41, "match"   # 門檻 0.447 以內視為高信心
  save_event(cam, path, decision, score)

  # 2) 回傳看板素材 URL(或直接 WebSocket 推送)
  return jsonify({
    "ok": True,
    "decision": decision,
    "score": score,
    "asset_url": "https://your-cdn/ad_pages/berry_tart_85.html"
  })

方案選擇:雲端 Amazon Rekognition(免自訓、boto3 易串接)或 多模態向量(延展性高)。報告內亦比較 Azure Face vs Rekognition 的授權/便利性。


4) 推播輸出(Kiosk/APP)與資料回寫

  • 數位看板:Chromecast with Google TV,Kiosk 模式播放廣告頁(URL 由後端決策產出)。
  • 資料回寫:把每次事件(時間、相似度、決策、素材)寫入 SQLite/雲端 DB,支援日/週報表。

ASCII:日報儀表模板

[今日轉換小結]
曝光 4,320 | 看板互動 372 (8.6%) | 推播 310 | 轉換 47 (15.2%)
高峰時段:12:00–14:00、18:00–21:00
熱門品類:甜點 / 健康飲品

5) 門檻與防誤觸(把「精準」放在系統裡)

向量門檻

  • distance < 0.447:高信心;< 0.632:中信心;超過則視為不同。建議中信心段落保守處理或人工複核。

通知漏斗

[有影像] → [距離過門檻?] → [偏好/品類匹配?] → [冷卻&AB Test] → ✅ 推播
                     ↑                      ↑
                0.447/0.632            避免洗頻

退避重試

抓取/上傳失敗 → 等 1s → 再試
若仍失敗 → 等 2s → 再試
再失敗 → 等 4s → 記錄 error_log 後暫停 1min

6) 雲端部署(Cloud Run/VM)與環境變數

最小相依(節錄)

Flask==3.0.3, gunicorn==21.2.0
requests==2.32.3, beautifulsoup4==4.12.3, lxml>=5.3
line-bot-sdk>=3.11,<4
google-cloud-firestore==2.16.0, grpcio>=1.74

Cloud Run 重要環境變數

LINE_CHANNEL_ACCESS_TOKEN, LINE_CHANNEL_SECRET
DEFAULT_PERIOD_SEC (預設監看間隔, e.g., 60)
ALWAYS_NOTIFY (0/1) | MAX_PER_TICK | TICK_SOFT_DEADLINE_SEC

Kiosk 設定:Google TV 以 Kiosk 模式開機即播指定 URL,避免人工干預。

7) 情境化 Demo

A. 甜點自由日

  • 向量匹配→判定為「甜點偏好」→ 看板顯示「莓果塔 85 折(限今日)」;APP(選配)同步票券。
  • KPI:看板互動率↑、該品類轉換率↑。

B. 運動後補給

  • 傍晚人流靠近健康飲品架 → 自動提高該品項出現率;若相似度中段,顯示匿名分群型推薦(不揭個資)。

C. 回購喚醒

  • 以回寫的交易向量找「距上次購買 ≥ 14 天」→ 提供加碼券(但限制冷卻,避免廣告疲勞)。

8) 安全與隱私

  • 最小化保存:優先保存向量與必要摘要,影像可即刻捨棄。
  • 門檻區間0.447–0.632 屬中信心,建議人工覆核或退回匿名分群邏輯。
  • 現場告知:張貼用途、保存、聯絡窗口,符合場域規範。

9) 今日交付 Checklist

  • [ ] ESP32-CAM → /upload 實測通了
  • [ ] 後端能回傳 decision/score/asset_url
  • [ ] 看板(Kiosk)可根據 URL 自動換頁
  • [ ] 日誌/SQLite 有事件紀錄,可做日報
  • [ ] 設好向量/人臉門檻與冷卻,避免洗頻

明天(Day 3)把「監控儀表+A/B 測試+ROI 觀測」一次補齊,幫你做出可長跑的營運版。


上一篇
系列主題Day 1|你的專屬折扣:用 AI 把「線下猶豫」變「線上轉換」——三天從商機洞察到上線驗證
下一篇
系列主題Day 3|你的專屬折扣:用 AI 把「線下猶豫」變「線上轉換」——三天從商機洞察到上線驗證
系列文
IT工具與自我學IT的過程分享30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言